#include "Client.hpp"

Client::Client(asio::io_context &io_context, const tcp::resolver::results_type &endclient)
    : m_io_Context(io_context),
      m_socket(io_context)
{
    DoConnet(endclient);
}

void Client::DoConnet(const tcp::resolver::results_type &endclient)
{
    asio::async_connect(m_socket, endclient,
                        [this](std::error_code ec, tcp::endpoint)
                        {
                            if (!ec)
                            {
                                DoReadHead();
                            }
                        });
}

void Client::DoReadHead()
{
    asio::async_read(m_socket,
                     asio::buffer(m_CurrentMessage.data(), Message::head_length),
                     [this](std::error_code ec, std::size_t) {
                         if (!ec && m_CurrentMessage.decode_header())
                         {
                             DoReadBody();
                         }
                         else
                         {
                             m_socket.close();
                         }
                     });
}

void Client::DoReadBody()
{
    asio::async_read(m_socket, 
                     asio::buffer(m_CurrentMessage.body(), m_CurrentMessage.body_length()),
                     [this](std::error_code ec, std::size_t)
                     {
                         if (!ec)
                         {
                             std::cout.write(m_CurrentMessage.body(), m_CurrentMessage.body_length());
                             std::cout << "\n";
                             DoReadHead();
                         }
                         else
                         {
                             m_socket.close();
                         }
                     });
}

void Client::DoWrite()
{
    asio::async_write(m_socket,
                      asio::buffer(m_ChatContext.front().data(),
                                   m_ChatContext.front().length()),
                      [this](std::error_code ec, std::size_t)
                      {
                          if (!ec)
                          {
                              m_ChatContext.pop_front();
                              if (!m_ChatContext.empty())
                              {
                                  DoWrite();
                              }
                          }
                          else
                          {
                              m_socket.close();
                          }
                      });
}

void Client::write(const Message &msg)
{
    asio::post(m_io_Context,
               [this, msg]()
               {
                   bool write_in_progress = !m_ChatContext.empty();
                   m_ChatContext.push_back(msg);
                   if (!write_in_progress)
                   {
                       DoWrite();
                   }
               });
}

void Client::close()
{
    asio::post(m_io_Context, [this]()
               { m_socket.close(); });
}

// ------ Program Enter Function --------//

int main(int argc, char **argv)
{
    try
    {
        if (argc != 3)
        {
            std::cerr << "Usage: Client <host> <port>\n";
            return 1;
        }
        asio::io_context io_context;

        tcp::resolver _resolver(io_context); // tcp Resolver

        auto endclient = _resolver.resolve(argv[1], argv[2]);
        Client c(io_context, endclient);

        std::thread t([&io_context]()
                      { io_context.run(); });
        char line[Message::max_body_length + 1]; // using for print chat message;
        while (std::cin.getline(line, Message::max_body_length + 1))
        {
            Message msg;
            msg.body_length(std::strlen(line));
            std::memcpy(msg.body(), line, msg.body_length());
            msg.encode_header();
            c.write(msg);
        }
    }
    catch (std::exception &e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}
